package gov.dhs.cis.soa.readers.reqpro.xmind;

import gov.dhs.cis.soa.Utils;
import gov.dhs.cis.soa.readers.reqpro.xmind.XMindObject.TYPE;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Hashtable;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XMindFileCreator {
	static final Logger loger = Logger.getLogger(XMindFileCreator.class);

	private String templateInputFileName = "template.xmind";
	protected String crtTime = ""+System.currentTimeMillis();

	int nextID = 1;

	public XMindFileCreator() {
	}
	
	public void reset() {
		crtTime = ""+System.currentTimeMillis();
		nextID = 1;
	}

	public void writeAll(XMindObject sData) {
		createXML(sData);
	}
	
	private void transform(Document document, String fileName) throws Exception {
	    TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
        DOMSource source = new DOMSource(document);
        OutputStream fos = null;
        if(!Utils.isEmpty(fileName)) {
        	//fos = new  FileOutputStream(fileName);
			ZipEntry ziEntry = null;
			try {
				URL inURL = this.getClass().getClassLoader().getResource(templateInputFileName);
				ZipOutputStream outStream = new ZipOutputStream(new FileOutputStream(fileName));
				ZipInputStream inStream = new ZipInputStream(new FileInputStream(inURL.getFile()));
				while ((ziEntry = inStream.getNextEntry()) != null) {
					//loger.debug(ziEntry.getName());
					ZipEntry zOEntry = new ZipEntry(ziEntry);
					zOEntry.setTime(Long.parseLong(crtTime));
					outStream.putNextEntry(zOEntry);
					if("content.xml".equalsIgnoreCase(ziEntry.getName())) {
				        StreamResult result =  new StreamResult(outStream);
				        transformer.transform(source, result);
					} else {
						int size;
						byte[] buffer = new byte[1024];
	
						while ((size = inStream.read(buffer, 0, buffer.length)) != -1) {
							outStream.write(buffer, 0, size);
						}
					}
					inStream.closeEntry();
					outStream.closeEntry();
				}
				inStream.close();
				outStream.close();
			} catch (Exception e) {
				e.printStackTrace();
				loger.debug(ziEntry.getName());
			}
       } else {
        	fos = System.out;       	
            StreamResult result =  new StreamResult(fos);
            transformer.transform(source, result);
       }
	}
	
	private Element addAllChildTopics(Document document, Element parentElement, XMindObject topicObj) {
		Element element = null;
		
		if(topicObj.objType == TYPE.TOPIC) {
			element = addTopicNode(document, parentElement, sizeTheTitleText(topicObj.name));
			if("RL".equalsIgnoreCase(topicObj.id))
				element.setAttribute("style-id", "239n0o05527dot1fucuf8cpdq2");
		} else if(topicObj.objType == TYPE.CHILDTOPIC) {
			element = addChildTopicNode(document, parentElement, (topicObj.id + ":" + topicObj.name), topicObj.descText);			
			//element = addChildTopicNode(document, parentElement, (topicObj.id + ":" + topicObj.name), null);			
		} 
		for(XMindObject childObj : topicObj.children) {
			Element childE = addAllChildTopics(document, element, childObj);			
		}
		
		return element;
	}
	private void createXML(XMindObject sData) {
		try {
			Hashtable<String, Element> nodes = new Hashtable<String, Element>();
			
			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
			Document document = documentBuilder.newDocument();
			Element rootElement = document.createElement("xmap-content");
			rootElement.setAttribute("xmlns", "urn:xmind:xmap:xmlns:content:2.0");
			rootElement.setAttribute("xmlns:fo", "http://www.w3.org/1999/XSL/Format");
			rootElement.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg");
			rootElement.setAttribute("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
			rootElement.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
			rootElement.setAttribute("version", "2.0");
			document.appendChild(rootElement);
			
			for(XMindObject xObj : sData.children) {
				if(xObj.objType != TYPE.SHEET) {
					System.out.println("Not a SHEET element, ignore " + xObj.name);
				}
				Element sheetElement = addSheetNode(document, rootElement, xObj.id);
				addTittleNode(document, sheetElement, xObj.name);
				for(XMindObject topicObj : xObj.children) {
					Element topicsElement = addAllChildTopics(document, sheetElement, topicObj);					
				}
			}
			transform(document, "c:/" + sData.name + ".xmind");			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void addTittleNode(Document document, Element sheetElement, String titleTxt) {
		if (Utils.isEmpty(titleTxt))
			return;
		Element txtElem = document.createElement("title");
		txtElem.appendChild(document.createTextNode(titleTxt));
		sheetElement.appendChild(txtElem);
	}

	private Element addTopicNode(Document document, Element parentElement, String topicTxt) {
		Element element = document.createElement("topic");
		element.setAttribute("id", "Topic"+(nextID++));
		element.setAttribute("timestamp", crtTime);
		if (!Utils.isEmpty(topicTxt)) {
			Element txtElem = document.createElement("title");
			txtElem.appendChild(document.createTextNode(topicTxt));
			element.appendChild(txtElem);
		}
		parentElement.appendChild(element);
		//element.appendChild(document.createElement("children"));
		return element;
	}

	private String sizeTheTitleText(String title) {
		int CUTOFF_LEN = 30;
		int len = title.length();
		String ret = "";
		int start = 0;
		int end = CUTOFF_LEN;
		
		while (len >= end) {
			ret += (title.substring(start, end) + "\n");
			start = end;
			if(end < len)
				end += CUTOFF_LEN;
			else if (end == len)
				break;
			if(end > len)
				end = len;
		}
		if(ret.length() == 0)
			ret = title;
		return ret;
	}
	private Element addSheetNode(Document document, Element parentElement, String sheetTitle) {
		Element sheetElement = document.createElement("sheet");
		sheetElement.setAttribute("id", "Sheet"+(nextID++));
		addTittleNode(document, sheetElement, sheetTitle);
		parentElement.appendChild(sheetElement);
		return sheetElement;
	}
	
	private Element addMarkerRefsNode(Document document, Element parentElement, int priority, String release) {
		Element markRefsElem = document.createElement("marker-refs");
		Element markRefElem = document.createElement("marker-ref");
		markRefElem.setAttribute("marker-id", "maturity-"+priority);
		markRefsElem.appendChild(markRefElem);
		if(!Utils.isEmpty(release)) {
			Element markRefElem2 = document.createElement("marker-ref");
			markRefElem2.setAttribute("marker-id", ("release-"+release.charAt(0)).toLowerCase());
			markRefsElem.appendChild(markRefElem2);
		}
		parentElement.appendChild(markRefsElem);
		return markRefsElem;
	}
	
	private Element findChildNode(Node parentMode, String nodeName, String titleTxt) {
		Element retElem = null;
		NodeList list = parentMode.getChildNodes();
		if(list == null || list.getLength() == 0)
			return retElem;
		for(int i = 0; i < list.getLength(); i++) {
			Node node = list.item(i);
			if(nodeName.equalsIgnoreCase(node.getNodeName())) {
				if(Utils.isEmpty(titleTxt))
					return (Element) node;
				retElem = findChildNode(node, "title", null);
				if(titleTxt.equalsIgnoreCase(retElem.getFirstChild().getNodeValue()))
					return retElem;
				retElem = null;
			}
		}
		return retElem;
	}
	private Element addChildTopicNode(Document document, Element parentElement, String childName, String description) {
		Element foundElement = null;
		Element topicsElement = null;
		Element childrenElement = findChildNode(parentElement, "children", null); //this is "children" node.
		if(childrenElement != null) {
			topicsElement = findChildNode(childrenElement, "topics", null); //this is "topics" node
			if(topicsElement != null && topicsElement.hasChildNodes()) {
				foundElement =  findChildNode(topicsElement, "topic", childName);
			}
		}
		if(foundElement == null) {
			if(childrenElement == null) {
				childrenElement = document.createElement("children");
				parentElement.appendChild(childrenElement);
			}
			if(topicsElement == null) {
				topicsElement = document.createElement("topics");
				topicsElement.setAttribute("type", "attached");
				childrenElement.appendChild(topicsElement);
			}
			foundElement = addTopicNode(document, topicsElement, childName);
		}
		//now add description..
		if(!Utils.isEmpty(description)) {
			String convDesc = removeAllNonPrintChars(description);
			Element notesElem = document.createElement("notes");
			Element htmlElem = document.createElement("html");
			Element plainElem = document.createElement("plain");
			Element xhtmlElem = document.createElement("xhtml:p");
			xhtmlElem.appendChild(document.createTextNode(convDesc));
			plainElem.appendChild(document.createTextNode(convDesc));
			htmlElem.appendChild(xhtmlElem);
			notesElem.appendChild(htmlElem);
			notesElem.appendChild(plainElem);
			foundElement.appendChild(notesElem);
		}
		/*
		<notes><html><xhtml:p>my notes go here</xhtml:p><xhtml:p/></html><plain>my notes go here&#13;
		</plain></notes>
		*/
		return foundElement;
	}

	private String removeAllNonPrintChars(String description) {
		String truncated = description.replaceAll("\\p{Cntrl}", "");
		return truncated;
	}
	
	public void test() {
		try {
			String outFileName = "c:/services.xmind";
			String inFileName = "template.xmind";
	        URL inURL = this.getClass().getClassLoader().getResource(inFileName);
			//copyFile("template.xmind", outFileName);
	        ZipOutputStream outStream = new ZipOutputStream(new FileOutputStream(outFileName));
			ZipInputStream inStream = new ZipInputStream(new FileInputStream(inURL.getFile()));
			ZipEntry ziEntry = null;
			while((ziEntry = inStream.getNextEntry()) != null) {
				loger.debug(ziEntry.getName());
				ZipEntry zOEntry = new ZipEntry(ziEntry);
				outStream.putNextEntry(zOEntry);
                int size;
                byte[] buffer = new byte[1024];

				while ((size = inStream.read(buffer, 0, buffer.length)) != -1) {
					outStream.write(buffer, 0, size);
                }
				inStream.closeEntry();
				outStream.closeEntry();
			}
			inStream.close();
			outStream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
